package core.proto.rightwindow;
import static app.constants.CollabVizConstants.*;

import java.applet.*;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.DisplayMode;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Line2D;
import java.beans.PropertyVetoException;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map.Entry;
import java.util.Properties;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JComponent;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButton;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.TransferHandler;
import javax.swing.colorchooser.AbstractColorChooserPanel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.InternalFrameEvent;
import javax.swing.event.InternalFrameListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;

import core.proto.InfoViz;
import core.proto.Util;
import core.proto.annotation.Annotation;
import core.proto.annotation.AnnotationUI;
import core.proto.annotation.DocAnnotationUI;
import core.util.logging.UserLogWriter;
import core.util.logging.constants.Action;
import core.util.logging.constants.Location;



public class BoardPane extends JDesktopPane implements MouseMotionListener, MouseListener,ActionListener, 
	Runnable, ComponentListener, FocusListener, InternalFrameListener, KeyListener, ChangeListener {

	public static final String INVALID_GROUP = "";

	private static BoardPane instance = null; // singleton 

	private HttpChatPanel chatPanel;
	private JColorChooser colorChooser;
	private JPanel fontChooser;
	private JButton colorButton;
	private JButton undoButton;
	private Point delta; // For handling selecting multiple frames. 

	private Rectangle mouseRect;
	private static List<PostIt> allPostIts;
	static Color FOREIGN_NOTE_COLOR;

	private static final String REGROUP = "regroup";
	private static final int WIDE = 800;
	private static final int HIGH = 600;
	
	public static final JToggleButton italic = new JToggleButton();
	public static final JToggleButton bold = new JToggleButton();
	public static final JToggleButton underline = new JToggleButton();
	public static final JButton bullet = new JButton();

	private boolean selecting = false;
	private Point mousePt;
	private JPopupMenu popup;
	
	private float smallestSize = 1.0f;
	private float smallSize = 1.4f;
	private float largeSize = 2.2f;
	private float largestSize = 2.6f;

	public PopupPanel popupEditor;

	public static Hashtable<String, List<PostIt>> postItGroups;

	private Thread postItUpdateProcessor; 
	private LinkedList<String> postItUpdates;
	private Hashtable<String, PostIt> foreignPostIts; // PostIt from other users. 

	public PostIt postItInFocus = null;

	private static final int dx = 15; 
	private static final int dy = 20;
	
	//GUI link size
	private static float linkSize = 2.0f;

	public final TransferHandler transferHandler; 
	
	private PostIt postItTimerDependent;

	// group is defined by screen position so this is just for convenience of storing groups. 
	private long groupCounter = 0;	

	private JLabel plusArea;
	public JLabel plusAreaSecondary;

	private static PostItStorer pis;
	
	public static ArrayList<postItPair> singleLinkMap = new ArrayList<postItPair>();
	public static HashMap<postItPair, Float> singleSizeMap = new HashMap<postItPair, Float>();
	
	//First post it stored when making a single link
	private static PostIt singlePostIt = null;
	
	//First post it SELECTED list when making a single link
	private static List<PostIt> singlePostItList = null;
	
	//FIRST group stored in order to make a group link
	public static List<PostIt> groupLinkList = new ArrayList<PostIt>();
	public static ArrayList<List<PostIt>> groupLinkListList = new ArrayList<List<PostIt>>();
	
	public static HashMap<String, List<String>> groupIDMap = new HashMap<String, List<String>>();
	public static HashMap<ArrayList<String>, Float> groupSizeMap = new HashMap<ArrayList<String>, Float>();
	
	private static double currMeX = 0.0;
	private static double currMeY = 0.0;
	private boolean rightClickLineRemoval = false;

	private Color timerDependentColor;

	public static Object lock = new Object ();
	
	//List of corresponding actions 
	private static ArrayList<HashMap<String, HashMap<String, Object>>> actionList = new ArrayList<HashMap<String, HashMap<String, Object>>>();

	public static BoardPane getInstance () {
		if (instance == null) {
			synchronized(lock) {
				if (instance == null){
					instance = new BoardPane();
				}
			}
		}
		return instance;
	}

	private BoardPane() {
		transferHandler = new PostItTransferHandler();
		this.setTransferHandler(transferHandler);
		this.setForeground(Color.GREEN);
		this.setOpaque(true);

		delta = new Point();

		foreignPostIts = new Hashtable<String, PostIt> ();

		mouseRect = new Rectangle();

		postItGroups = new Hashtable<String, List<PostIt>>();

		popup = new JPopupMenu();
		JMenuItem menuItem = new JMenuItem(REGROUP);
		menuItem.addActionListener(this);
		popup.add(menuItem);

		allPostIts = new ArrayList<PostIt>();
		mousePt = new Point(WIDE / 2, HIGH / 2);

		setDragMode(OUTLINE_DRAG_MODE);
		this.setSize(WIDE,HIGH);

		this.setLayout(null);
		this.addMouseMotionListener(this);
		this.addMouseListener(this);
		this.addKeyListener(this);

		popupEditor = new PopupPanel(this);
		this.add(popupEditor, JLayeredPane.PALETTE_LAYER);
		popupEditor.setVisible(false);

		setUpBayArea ();
		setUpChatPanel ();
		setUpColorChooser();
		setUpFontPanel();
		addUndoButton();
		addColorChooserButton();

		if(InfoViz.UserID.equals("Alex")){
			FOREIGN_NOTE_COLOR = PAT_COLOR;
			PostIt.chooseColor(ALEX_COLOR);
		} else if(InfoViz.UserID.equals("Pat")){
			FOREIGN_NOTE_COLOR = ALEX_COLOR;
			PostIt.chooseColor(PAT_COLOR);
		} else{
			FOREIGN_NOTE_COLOR = new Color(195, 217, 255);
		}
		
		postItUpdateProcessor = this.createPostItUpdateThread();
		postItUpdates = new LinkedList<String>();
		postItUpdateProcessor.start();
		
		
		new Thread(this).start();
		
		//When a new user logs in, present the entire whiteboard
		new Thread(
	            new Runnable() {
	                public void run() {
	                	while(true){
	                		String newUser = null;
	                		while(newUser == null){
	                			InputStream in;
								try {
									in = WebUtil.pullNewUser();
									BufferedReader data = new BufferedReader(new InputStreamReader(in)); 
		            				newUser = data.readLine(); 
								} catch (IOException e) {
									e.printStackTrace();
								}
	            				
	                		}
	                		
	                		if(!newUser.equals(InfoViz.UserID) && 
	                				((BoardPane.getAllPostIts()!= null || !BoardPane.getAllPostIts().isEmpty())
	                				|| (foreignPostIts != null || !foreignPostIts.isEmpty()))){
		                		for(PostIt p : BoardPane.getAllPostIts()){
		                			LeftRightCommunicator.postItUpdated(p);
		                		}
		                		for(String key : foreignPostIts.keySet()){
		                			LeftRightCommunicator.postItUpdated(foreignPostIts.get(key));
		                		}
	                		}
	                	}
	                }
	            }).start();
		
		//Request all postits currently in other user's systems
		try {
			WebUtil.sendNewUser(InfoViz.UserID);
		} catch (IOException e1) {
			e1.printStackTrace();
		} catch (IllegalArgumentException e){
			
		}
		
	}
	
	public void loadStoredPostits(){
		ArrayList<PostIt> list2Sort = null;
		ArrayList<PostIt> storedList = new ArrayList<PostIt>();
		
		try{
			list2Sort = PostItStorer.getStoredPostits();
			Collections.sort(list2Sort);
			
			for(int i = list2Sort.size(); i > 0; i--){
				storedList.add(storedList.size(), list2Sort.get(i - 1));
			}
			
		}
		catch(Exception e){
			System.out.println("EXCEPTION OCCURED " + e.getMessage());
		}
		
		if(list2Sort != null){
			for(PostIt pi : storedList){
				if(pi != null){
					
					final PostIt post = new PostIt(InfoViz.userToken, false);
					
					//SaveState
						HashMap<String, Object> out = new HashMap<String, Object> ();
						out.put("post", post);
						saveState("create", out);
						
						post.setBounds(pi.getX(), pi.getY(), pi.getWidth(), pi.getHeight());
					
					Point p;
					if (this.overlapsWithForbiddenAreas(post)) {
						// Ignore mouse clicks that ends up creating post it that 
						// overlaps with plus area or the chat area. 
						p = getClosestGoodLocation(post);
						post.setLocation(p);
					}
					else{
						p = pi.getLocation();
					}
					
					post.getArea().setText(pi.getText());
					post.getArea().setBackground(pi.getArea().getBackground());
					post.setSessionID(pi.getSessionId());
					addPostIt(post, p);
					
					try {
						post.setSelected(true);
					} catch (PropertyVetoException e) {
					}
					post.toFront();
					
					UserLogWriter.log2(Action.CLICK, Location.Whiteboard.toString(), "Sticky", "null", 
							"null", "null", "New", pi.delimText());
				}
			}
		}
		
		
		pis = new PostItStorer();
		pis.handleStoredLinks();
	}

	private void setUpFontPanel() {
		
		fontChooser = new JPanel();
		

		JMenuBar menuBar = new JMenuBar();
		JMenu menu = new JMenu();
		    
		JRadioButtonMenuItem smallest = new JRadioButtonMenuItem(new ImageIcon(Util.getServerResource("resources/img/smallestLine.png")), false);
		smallest.setActionCommand("smallest");
		smallest.addActionListener(this);
		smallest.setDisabledIcon(new ImageIcon(Util.getServerResource("resources/img/smallestLineAlt.png")));
		    
		JRadioButtonMenuItem large = new JRadioButtonMenuItem(new ImageIcon(Util.getServerResource("resources/img/largeLine.png")), true);
		large.setActionCommand("large");
		large.addActionListener(this);
		large.setDisabledIcon(new ImageIcon(Util.getServerResource("resources/img/largeLineAlt.png")));
		    
		ButtonGroup group = new ButtonGroup();
		group.add(smallest);
		group.add(large);
			
		menu.add(smallest);
		menu.add(large);
			
		menuBar.add(menu);
			
		group.setSelected(large.getModel(), true);
			
		fontChooser.add(smallest);
		fontChooser.add(large);
		
		bold.addActionListener(new ActionListener() {
		    @Override
		    public void actionPerformed(ActionEvent evt) {
		        UserLogWriter.log2(Action.CLICK, Location.Whiteboard.toString(), "Font and Link Panel", 
						"Bold", "null", "null", "null", "null");
		    }
		});
		bold.setIcon(new ImageIcon(Util.getServerResource(
				"resources/img/bold.png")));
		fontChooser.add(bold);

		italic.addActionListener(new ActionListener() {
		    @Override
		    public void actionPerformed(ActionEvent evt) {
		    	UserLogWriter.log2(Action.CLICK, Location.Whiteboard.toString(), "Font and Link Panel", 
						"Italic", "null", "null", "null", "null");
		    }
		});
		italic.setIcon(new ImageIcon(Util.getServerResource(
				"resources/img/italic.png")));
		fontChooser.add(italic);
		
		underline.addActionListener(new ActionListener() {
		    @Override
		    public void actionPerformed(ActionEvent evt) {
		    	UserLogWriter.log2(Action.CLICK, Location.Whiteboard.toString(), "Font and Link Panel", 
						"Underline", "null", "null", "null", "null");
		    }
		});
		underline.setIcon(new ImageIcon(Util.getServerResource(
				"resources/img/underline.png")));
		fontChooser.add(underline);
		
		bullet.addActionListener(new ActionListener() {
		    @Override
		    public void actionPerformed(ActionEvent evt) {
		        	PostIt.createBullet();
		        	 UserLogWriter.log2(Action.CLICK, Location.Whiteboard.toString(), "Font and Link Panel", 
		 					"Bullet Point", "null", "null", "null", "null");
		    }
		});
		bullet.setIcon(new ImageIcon(Util.getServerResource(
				"resources/img/bullet.png")));
		fontChooser.add(bullet);
		
		this.add(fontChooser, JLayeredPane.DEFAULT_LAYER);
		fontChooser.setBounds(getSecondScreenWidth() - 50, 130, 40, 230);
		fontChooser.setVisible(true);
	}

	private void addUndoButton() {
		undoButton = new JButton();
		undoButton.addActionListener(this);
		undoButton.setVisible(true);
		undoButton.setSize(35, 35);
		Icon icon = new ImageIcon(Util.getServerResource(
				"resources/img/undo.png"));
		undoButton.setIcon(icon);
		this.add(undoButton, JLayeredPane.DEFAULT_LAYER);
		undoButton.setLocation(getSecondScreenWidth() - 50, 0);
	}
	
	private void addColorChooserButton() {
		colorButton = new JButton();
		colorButton.addActionListener(this);
		colorButton.setVisible(true);
		colorButton.setSize(35, 35);
		Icon icon = new ImageIcon(Util.getServerResource(
				"resources/img/pallet.png"));
		colorButton.setIcon(icon);
		this.add(colorButton, JLayeredPane.DEFAULT_LAYER);
		
		colorButton.setLocation(getSecondScreenWidth() - 50, 35);

	}

	private int getSecondScreenWidth(){
		GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
		GraphicsDevice[] gs = ge.getScreenDevices();
		DisplayMode dm;

		// Get size of each screen
		try{
			 dm = gs[1].getDisplayMode();
		}
		catch(ArrayIndexOutOfBoundsException e){
			 dm = gs[0].getDisplayMode();
		}
		
		 int screenWidth = dm.getWidth();
		return screenWidth;
	}
	
	private void setUpColorChooser() {
		colorChooser = new JColorChooser();

		//Add new custom chooser panel
		AbstractColorChooserPanel panels[] = { new ColorChooser() };
        colorChooser.setChooserPanels(panels);
        
		colorChooser.setPreviewPanel(null);
		
		// Retrieve the current set of panels
		AbstractColorChooserPanel[] oldPanels = colorChooser.getChooserPanels();

		// Remove panels
		for (int i=0; i<oldPanels.length; i++) {
		    String clsName = oldPanels[i].getClass().getName();
		    if (clsName.equals("javax.swing.colorchooser.DefaultRGBChooserPanel")) {
		        // Remove rgb chooser if desired
		        colorChooser.removeChooserPanel(oldPanels[i]);
		    } else if (clsName.equals("javax.swing.colorchooser.DefaultHSBChooserPanel")) {
		        // Remove hsb chooser if desired
		        colorChooser.removeChooserPanel(oldPanels[i]);
		    }
		}
		
		colorChooser.setPreviewPanel(new JPanel());
		colorChooser.setVisible(false);
		colorChooser.getSelectionModel().addChangeListener(this);
       
		int colorW = 107;
		 int colorX = getSecondScreenWidth() - colorW - 15 - 35;
		int colorH = 60;
		int colorY = 35;
		colorChooser.setBounds(colorX, colorY, colorW, colorH);
		this.add(colorChooser, JLayeredPane.DEFAULT_LAYER);
		
	}

	private void setUpBayArea () {
		this.plusArea = new JLabel ();
		plusArea.setBounds(0,0,PostIt.SIDE_LENGTH, PostIt.SIDE_LENGTH);
		plusArea.setOpaque(false);	// transparent
		plusArea.setBorder(BorderFactory.createLineBorder(Color.black));

		plusArea.setHorizontalAlignment(JLabel.CENTER);
		plusArea.setVerticalAlignment(JLabel.CENTER);
		
		GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] gs = ge.getScreenDevices();

        GraphicsDevice right = gs.length > 1 ? gs[1] : gs[0];
		this.plusAreaSecondary = new JLabel ();
		int y = right.getDefaultConfiguration().getBounds().height;
		int x = right.getDefaultConfiguration().getBounds().width;
        
		//plusAreaSecondary.setBounds(0,PostIt.SIDE_LENGTH + 20,PostIt.SIDE_LENGTH, PostIt.SIDE_LENGTH);
		plusAreaSecondary.setBounds(x - PostIt.SIDE_LENGTH - 20, y - PostIt.SIDE_LENGTH - 48,PostIt.SIDE_LENGTH, PostIt.SIDE_LENGTH);
		plusAreaSecondary.setOpaque(false);	// transparent
		plusAreaSecondary.setBorder(BorderFactory.createLineBorder(Color.black));

		plusAreaSecondary.setHorizontalAlignment(JLabel.CENTER);
		plusAreaSecondary.setVerticalAlignment(JLabel.CENTER);
		
		if(InfoViz.UserID.equals("Alex")){
			this.add(plusArea, JLayeredPane.DEFAULT_LAYER);
			
			if(InfoViz.sharePostits){
				this.add(plusAreaSecondary, JLayeredPane.DEFAULT_LAYER);
			}
		}
		else{
			this.add(plusAreaSecondary, JLayeredPane.DEFAULT_LAYER);
			
			if(InfoViz.sharePostits){
				this.add(plusArea, JLayeredPane.DEFAULT_LAYER);
			}
		}
	}

	private void setUpChatPanel () {
		chatPanel = new HttpChatPanel();
		int chatX = 0;
		int chatW = (int) (this.getWidth() * CHAT_WIDTH_PERCENT);
		int chatH = (int) (this.getHeight() * (CHAT_HEIGHT_PERCENT));
		
		int chatY = this.getHeight() + 110;
		
		chatPanel.setBounds(chatX, chatY, chatW, chatH);
		chatPanel.startListenerThread();
		
		this.add(chatPanel, JLayeredPane.DEFAULT_LAYER);
	}

	// Creates the thread that would process the post it updates 
	// that we pulled from the server. 
	private Thread createPostItUpdateThread () {
		return new Thread() {
			LinkedList<String> toProcess = new LinkedList<String>();

			public void run () {
				while (true) {
					try {
						// no need to put in a while loop
						// because this is the only thread waiting on it.
						// The synchronized block needed because this thread is not the owner 
						// of postItUpdates and security manager will bark otherwise and give exception. 
						synchronized(postItUpdates){
							postItUpdates.wait();
							toProcess.addAll(postItUpdates);
							postItUpdates.clear();
						}
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

					// Run in the event processin thread's context to avoid having to worry about 
					// concurrency issues with the white board's data structures. 
					SwingUtilities.invokeLater(new Runnable(){

						private void handleForeignPostIt (Properties props, String networkString) {
							String key = PostItNetworking.getUniqueKey(props);
							String content = PostItNetworking.getMessage(props);
							PostIt pi;
										
							if (! foreignPostIts.containsKey(key)){

								// Add new post it!
								Util.p("Debug: Adding postit from network");
								//Save state for undofunction
								
								pi = new PostIt(networkString);
								pi.preventBounceBack(true);
								
								if(PostItNetworking.getLocalSharing(props).equals("true")){
									pi.setForeignSharing(true);
									
									pi.setVisible(true);
								}
								
								//SaveState
								HashMap<String, Object> out = new HashMap<String, Object> ();
								out.put("post", pi);
								saveState("create", out);
		
								pi.setText(content);
								BoardPane.getInstance().addPostIt(pi, PostItNetworking.getLocation(props));
								pi.setLocation(PostItNetworking.getLocation(props));
								foreignPostIts.put(key, pi);
								
								//Sets location for Pat created postits HERES 
								if(InfoViz.UserID.equals("Alex")){
									pi.lastToolPos = BoardPane.getInstance().plusAreaSecondary.getLocation();
								}
								
								AudioClip ac = InfoViz.newAudioClip(Util.getServerResource("resources/audio/postit.wav"));
								ac.play(); 
								try{
									Thread.sleep(300);
								}
								catch(Exception e){
									
								}
								ac.stop();
							} else {
								// look up the post it using its key Color.white
								pi = foreignPostIts.get(key);
								pi.preventBounceBack(true);
								
								if(PostItNetworking.getLocalSharing(props).equals("true")){
									pi.setForeignSharing(true);
									
									pi.setVisible(true);
								}

									pi.setText(content);
									// ComponentMoved gets triggered each time we change 
									// the location. In this case we don't want it to 
									// broadcast the change of location because the 
									// change didn't originate locally. 
									pi.setJustChangedLocOvetNetwork(true);
									pi.setLocation(PostItNetworking.getLocation(props));
								
							}
							
							processUpdates(pi, props);
						}

						private void handleLocalPostIt (Properties props) {
							long sid = PostItNetworking.getSid(props);
							for(PostIt p : allPostIts){
								if (p.getSessionId() == sid){
									// ComponentMoved gets triggered each time we change 
									// the location. In this case we don't want it to 
									// broadcast the change of location because the 
									// change didn't originate locally. 
									
									if(PostItNetworking.getLocalSharing(props).equals("true")){
										p.setForeignSharing(true);
										
										p.setVisible(true);
									}
									
									p.preventBounceBack(true);
									p.setJustChangedLocOvetNetwork(true);
									p.setLocation(PostItNetworking.getLocation(props));
									
									processUpdates(p, props);
								}
							}
						}

						@Override
						/**
						 * PostIt update processor thread. 
						 */
						public void run() {
							for(String networkString : ((LinkedList<String>)toProcess.clone())) {

								// At this point there are two kinds of updates. 
								// First kind: owner updates text and pushes it out. 
								// Second kind: either user moves the post it around and we need to mirror the screen positions. 

								// First extract the properties object. 
								
								Properties props = PostIt.toProperties(networkString);
								if (props == null) // malformed input. 
									continue;

								// Ignore messages broadcasted back to self.  
								String tok = PostItNetworking.getSenderToken(props);
								if (tok.equals(InfoViz.userToken)){
									continue;
								}
								
								synchronized(lock){
									if(((!PostItNetworking.getToDelete(props) || PostItNetworking.getLocation(props).x == -1))){
										String owner = PostIt.getOwnerToken(props);
										if (owner.equals(InfoViz.userToken)){
											this.handleLocalPostIt(props);
										} else {
											handleForeignPostIt(props, networkString);
										}
										
										
									}
									else if (!PostItNetworking.getSenderToken(props).equals(InfoViz.UserID)){
										PostIt toDel = null;
										
										for(PostIt p: BoardPane.getAllPostIts()){
											if(p.getSessionId() == PostItNetworking.getSid(props)){
												toDel = p;
											}
										}
										if(toDel != null){
											toDel.stopThread();
											foreignPostIts.remove(PostItNetworking.getUniqueKey(props));
											toDel.delete();
										}
										
									}
								
								}
							}
							toProcess.clear();
						}
					});
				}
			}
		};
	}
	
	private void processUpdates(PostIt pi, Properties props){
		ArrayList<String> outS = PostIt.getLinks(props, "singlelinks_out");
		ArrayList<PostIt> outSPost = new ArrayList<PostIt>();
		ArrayList<Float> outSWeight = PostIt.getWeights(props, "singlelinks_out");
				
		ArrayList<String> inS = PostIt.getLinks(props, "singlelinks_in");
		ArrayList<PostIt> inSPost = new ArrayList<PostIt>();
		ArrayList<Float> inSWeight = PostIt.getWeights(props, "singlelinks_in");
		
		ArrayList<String> outG = PostIt.getLinks(props, "grouplinks_out");
		ArrayList<String> inG = PostIt.getLinks(props, "grouplinks_in");
		
		ArrayList<Float> outGWeight = PostIt.getWeights(props, "grouplinks_out");
		ArrayList<Float> inGWeight = PostIt.getWeights(props,  "grouplinks_in");
		
		BoardPane.getInstance().determineAndAddToGroup(pi, pi.getLocation());
		String g = pi.getGroup();
		List<PostIt> overlaps = this.findOverlaps(pi);
		overlaps.add(pi);
		for(PostIt p : overlaps){
			if(postItGroups.contains(g)){
				if(!postItGroups.get(g).contains(p)){
					postItGroups.get(g).add(p);
				}
			}
			else{
				ArrayList<PostIt> out = new ArrayList<PostIt>();
				out.add(p);
				postItGroups.put(g, out);
			}
		}
		
		//Prevent updates in other elements of the stack from sending looping updates
		//Also ensure group name consistency
		try{
			for(PostIt p : postItGroups.get(pi.getGroup())){
				String group = PostItNetworking.getGroup(props);
				
				//Remove postit from false group
				if(!group.equals(p.getGroup())){
					if(postItGroups.contains(p.getGroup())){
						postItGroups.get(p.getGroup()).remove(p);
					}
				}
				
				p.setGroup(group);
				
				if(postItGroups.contains(group)){
					if(!postItGroups.get(group).contains(p)){
						postItGroups.get(group).add(p);
					}
				}
				else{
					ArrayList<PostIt> out = new ArrayList<PostIt>();
					out.add(p);
					postItGroups.put(group, out);
				}
				
				if(p != pi){
					p.piNetworker.oldNewUpdate();
				}
			}
		}
		catch(NullPointerException e){
			
		}
		
		if(BoardPane.getInstance().overlapsWithForbiddenAreas(pi)){
			pi.restoreToLastToolbarLocation();
		}
		
		pi.setOwnerToken(PostIt.getOwnerToken(props));

		
		//Add posit to appropriate group and ensure its place in the map
		String group = PostItNetworking.getGroup(props);
		
		//Load fonts
		PostItNetworking.loadFonts(props, pi.getArea());
		
		//Retrieve all postits to single link to
		for(PostIt p : BoardPane.getAllPostIts()){
			if(outS.contains(""+p.getSessionId())){
				outSPost.add(p);
			}
			if(inS.contains(""+p.getSessionId())){
				inSPost.add(p);
			}
		}
		
		//Remove all singleLinks involving pi to start fresh
		for(postItPair pair : ((ArrayList<postItPair>)singleLinkMap.clone())){
			if(pair.a == pi || pair.b == pi){
				singleLinkMap.remove(pair);
			}
		}
		
		int index = 0;
		//Add all referenced single links by pi
		for(PostIt out : outSPost){
			postItPair outPair = new postItPair(pi, out);
			singleLinkMap.add(outPair);
			singleSizeMap.put(outPair, outSWeight.get(index));
		}
		index = 0;
		for(PostIt in : inSPost){
			postItPair inPair = new postItPair(in, pi);
			singleLinkMap.add(inPair);
			singleSizeMap.put(inPair, inSWeight.get(index));
		}
		
		
		//Remove all groupLinks involving pi to start fresh
		for(String key : ((HashMap<String, List<String>>) groupIDMap.clone()).keySet()){
			if(key.equals(group)){
				groupIDMap.remove(key);
			}
			else if(groupIDMap.get(key).contains(group)){
				groupIDMap.get(key).remove(group);
			}
		}
		
		
		//Add all referenced group links by pi
		ArrayList<String> outList = new ArrayList<String>();
		ArrayList<String> sizeList = new ArrayList<String>();
		for(String out : outG){
			outList.add(out);
		}
		groupIDMap.put(group, outList);
		for(int i = 0; i < outG.size(); i++){
			sizeList.add(group);
			sizeList.add(outList.get(i));
			groupSizeMap.put(sizeList, outGWeight.get(i));
			sizeList = new ArrayList<String>();
		}
		
		
		for(int i = 0; i < inG.size(); i++){
			groupIDMap.get(inG.get(i)).add(group);
			sizeList.add(inG.get(i));
			sizeList.add(group);
			groupSizeMap.put(sizeList, inGWeight.get(i));
			sizeList = new ArrayList<String>();
		}
		
		//Set to ministate
		if((PostItNetworking.getMinimize(props) && !pi.miniState) || (!PostItNetworking.getMinimize(props) && pi.miniState)){
			pi.minimize();
		}
		
		//FULL MAP MEASURE
		postItGroups = new Hashtable<String, List<PostIt>>();
		for(PostIt p: allPostIts){
			String recGroup = p.getGroup();
			
			if(postItGroups.contains(recGroup)){
				postItGroups.get(recGroup).add(p);
			}
			else{
				ArrayList<PostIt> insertList = new ArrayList<PostIt>();
				insertList.add(p);
				postItGroups.put(recGroup, insertList);
			}
		}
		
		for(PostIt p: allPostIts){
			p.piNetworker.oldNewUpdate();
		}
		pi.preventBounceBack(false);
		repaint();
		
		if(PostItNetworking.getLocalSharing(props).equals("false")){
			pi.setForeignSharing(false);
			pi.setVisible(false);
		}
		
		this.repaint();
	}

	private long getNextGroup () {
		return groupCounter++;
	}

	public static List<PostIt> getNodes() {
		return allPostIts;
	}

	public static void setNodes(List<PostIt> nodes) {
		BoardPane.allPostIts = nodes;
	}

	public void removePostIt (PostIt pi) {
		// in critical section because this can be updated asynchronously 
		// when the remote user's postIt is deleted. 
		allPostIts.remove(pi);
		
		this.remove(pi);
		this.repaint();
		
		//REMOVE SINGLE LINK
		for(postItPair pair : ((ArrayList<postItPair>)singleLinkMap.clone())){
			if(pair.contains(pi)){
				singleLinkMap.remove(pair);
				singleSizeMap.remove(pair);
			}
		}
		
		removeFromGroup(pi);
		
	}
	
	public void removeAllLinks(PostIt pi){
		//REMOVE SINGLE LINK
				for(postItPair pair : ((ArrayList<postItPair>)singleLinkMap.clone())){
					if(pair.contains(pi)){
						singleLinkMap.remove(pair);
						singleSizeMap.remove(pair);
					}
				}
				
			BoardPane.getInstance().repaint();
	}

	/**
	 * Side effect: remove pi from the group it belongs to. 
	 * Also delete the group if no more post its belong to that group. 
	 * @param pi
	 */
	private void removeFromGroup (PostIt pi) {
		if (postItGroups.containsKey(pi.getGroup())){
			postItGroups.get(pi.getGroup()).remove(pi);
			if (postItGroups.get(pi.getGroup()).isEmpty()) {
				postItGroups.remove(pi.getGroup());
			}
		}
	}

	private void cascadePostIts (final List<PostIt> theSelected, double pivotX, double pivotY, boolean sort, boolean withAnimation) {
		if (sort)
			Collections.sort(theSelected);

		final int ANIMATION_FREQ = 200;

		if (withAnimation) {
			ActionListener listener = new ActionListener() {
				public Timer timer; 
				int curIndex = 0;
				{
					timer = new Timer(ANIMATION_FREQ, this);
					timer.start();
				}

				// Animation event.
				@Override
				public void actionPerformed(ActionEvent arg0) {
					++curIndex;
					if (curIndex < theSelected.size())
						timer.restart();
				}

			};
		}

		for (int i = 0; i < theSelected.size(); i++) {
			PostIt current = theSelected.get(i);
			current.setLocation((int) pivotX, (int) pivotY);
			
			try {
				current.setSelected(false);
			} catch (PropertyVetoException e) {
			}
			
			if(overlapsWithForbiddenAreas(current)){
				boolean allLegalPositions = true;
				
				for(int j = 0; j <= i; j++){
					PostIt moving = theSelected.get(j);
					moving.setLocation(moving.getX() + dx, moving.getY() + dy);
				}
			}
			
			pivotX = current.getX() - dx;
			pivotY = current.getY()- dy;
		}
		for(int i=theSelected.size()-1; i>-1; i--) {
			PostIt current = theSelected.get(i);
			current.toFront();
		}
	}



	//todowei fix cascading to avoid ever overlapping the rectangle 
	// containing the chat area and the plus area. 

	/**
	 * Cascade in the order given. 
	 * @param theSelected
	 */
	private void cascadePostIts (List<PostIt> theSelected, PostIt topMost) {
		// Sort it from most recent to least recently added.
		Collections.sort(theSelected);
		
		theSelected.remove(topMost);
		theSelected.add(0, topMost);
		
		if (theSelected.size() > 1) {
			// Coordinates of the most recently created PostIt. 
			int pivotX = theSelected.get(0).getX();
			int pivotY = theSelected.get(0).getY();

			if (pivotY - dy * (theSelected.size() - 1) < 0) {
				pivotY = dy * (theSelected.size()  - 1);
			}

			if (pivotX - dx * (theSelected.size() - 1) < 0) {
				pivotX = dx * (theSelected.size() - 1);
			}

			cascadePostIts(theSelected, pivotX, pivotY, false, false);		
		}

	}

	private boolean stopEditing (ActionEvent e) {
		if (this.postItInFocus != null) {
			if(e.getSource() != postItInFocus.popup && popupEditor.isVisible()){
				popupEditor.setVisible(false);
			}
			return stopEditing();
		}
		else if(popupEditor.isVisible()){
			popupEditor.setVisible(false);
		}
		return false;
	}
	
	/**
	 * 
	 * @return Whether we had a post it in focus when this was called.   
	 */
	private boolean stopEditing () {
		if (this.postItInFocus != null) {
			if (popupEditor.isVisible()) {
				// the setVisible method would do the side effects of syncing with 
				// left window and over network. 
			} else {
				postItInFocus.makeUnEditable();
			}
			try {
				this.postItInFocus.setSelected(false);
			} catch (PropertyVetoException e) {
			}
			this.postItInFocus = null;
			return true;
		}
		return false;
	}

	public void actionPerformed(ActionEvent e) {
		String cmd = e.getActionCommand();

		if (REGROUP.equals(cmd)) {
			stopEditing(e);

			List<PostIt> selected = new LinkedList<PostIt>();
			PostIt.getSelect(allPostIts, selected);
			if (selected.size() > 0) {	
				//this.mergeIntoOneGroup(selected, groupToMergeInto);
				for(PostIt pi:allPostIts) {
					// Restore the color to the normal ones.
					pi.restoreNormalColor ();
				}
			} else {
				// Nothing selected, cascade every group. 
				for(Entry<String,List<PostIt>> entry : postItGroups.entrySet()) {
					List<PostIt> postIts = entry.getValue();
					//this.cascadePostIts(postIts);
				}
			}
		}
		if(e.getSource() instanceof JButton){
			if((JButton)e.getSource() == undoButton){
				undo();
				UserLogWriter.log2(Action.CLICK, Location.Whiteboard.toString(), "Undo Button", 
						"null", "null", "null", "null", "null");
			}
			else if((JButton)e.getSource() == colorButton){
				if(colorChooser.isVisible()){
					colorChooser.setVisible(false);
				}
				else{
					colorChooser.setVisible(true);
				}
				
				UserLogWriter.log2(Action.CLICK, Location.Whiteboard.toString(), "Color Pallet Button", 
						"null", "null", "null", "null", "null");
			}
		}
		
		if(e.getActionCommand().equals("smallest")){
			linkSize = smallestSize;
			UserLogWriter.log2(Action.CLICK, Location.Whiteboard.toString(), "Font and Link Panel", 
					"Small Link", "null", "null", "null", "null");
		}
		else if(e.getActionCommand().equals("small")){
			linkSize = smallSize;
		}
		else if(e.getActionCommand().equals("large")){
			linkSize = largeSize;
			UserLogWriter.log2(Action.CLICK, Location.Whiteboard.toString(), "Font and Link Panel", 
					"Large Link", "null", "null", "null", "null");
		}
		else if(e.getActionCommand().equals("largest")){
			linkSize = largestSize;
		}
		
		this.repaint();
	}

	/**
	 * Add the PostIt to the white board. 
	 * All external users must call this method to add a post it.  
	 * @param pi
	 */
	public void addPostItFromLeftWindow(PostIt pi) {
		if(InfoViz.UserID.equals("Pat")){
			this.addPostIt(pi, this.plusAreaSecondary.getLocation());
		}
		else{
			this.addPostIt(pi, this.plusArea.getLocation());
		}
	}

	private void addPostIt(PostIt pi, Point location) {
		pi.parent = this;
		pi.setLocation(location);
		pi.setOrigin(location);
		allPostIts.add(pi);
		determineAndAddToGroup (pi, location);
		if(findOverlaps(pi).size() > 1){
			checkAndShiftGroup(pi);
		}
		pi.setVisible(true);
		this.add(pi, JLayeredPane.DEFAULT_LAYER);
		pi.addComponentListener(this);
		pi.addFocusListener(this);
		pi.addInternalFrameListener(this);
		repaint();
	}



	@Override
	public void mouseClicked(MouseEvent me) {
		// If we were editing something then stop. 
		if (stopEditing()) {
			return;
		}
		if(me.getClickCount() == 1) {
			boolean selBool = false;
			
			popupEditor.setVisible(false);
			
			for(PostIt p: allPostIts){
				if(p.isSelect()){
					selBool = true;
				}
			}
			
			if(selBool){
				PostIt.unselectAll(allPostIts);
				UserLogWriter.log2(Action.CLICK, Location.Whiteboard.toString(), "null", "null", 
						"null", "null", "null", "null");
			}
			else if(me.getModifiers() == InputEvent.BUTTON1_MASK){
				
				final PostIt pi = new PostIt(InfoViz.userToken, false);
				
				//SaveState
					HashMap<String, Object> out = new HashMap<String, Object> ();
					out.put("post", pi);
					saveState("create", out);
					
				pi.setBounds(me.getX(), me.getY(), pi.getWidth(), pi.getHeight());
				
				Point p;
				if (this.overlapsWithForbiddenAreas(pi)) {
					// Ignore mouse clicks that ends up creating post it that 
					// overlaps with plus area or the chat area. 
					p = getClosestGoodLocation(pi);
					pi.setLocation(p);
				}
				else{
					p = me.getPoint();
				}
				
				addPostIt(pi, p);
				
				try {
					pi.setSelected(true);
				} catch (PropertyVetoException e) {
				}
				pi.toFront();
				
				UserLogWriter.log2(Action.CLICK, Location.Whiteboard.toString(), "Sticky", "null", 
						"null", "null", "New", pi.delimText());
			}
		}
		
		BoardPane.getInstance().repaint();
	}

	@Override
	public void mouseEntered(MouseEvent arg0) {	}

	@Override
	public void mouseExited(MouseEvent arg0) {	}

	@Override
	public void mousePressed(MouseEvent e) {

		mousePt = e.getPoint();
		if (e.isShiftDown()) {
			PostIt.selectToggle(allPostIts, mousePt);
		} else if (e.isPopupTrigger() && postItGroups.entrySet().size() > 1) {
			//ERIC, disabled, see popup button
			//showPopup(e);
		}else if(e.getModifiers() == InputEvent.BUTTON3_MASK){
			currMeX= e.getX();
			currMeY = e.getY();
			rightClickLineRemoval = true;
		} 
		else {
			selecting = true;
		}
		e.getComponent().repaint();		
		
		
		
		BoardPane.getInstance().repaint();
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		selecting = false;
		mouseRect.setBounds(0, 0, 0, 0);
		List<PostIt> selected = new LinkedList<PostIt>();
		PostIt.getSelect(allPostIts, selected);
		
		for(PostIt p: BoardPane.getAllPostIts()){
			p.dragEnabled = false;
		}
		if (e.isPopupTrigger() && selected.size() > 0) {
			//ERIC, disabled, see popup button
			//showPopup(e);
		}
		e.getComponent().repaint();		
	}

	

	private void showPopup(MouseEvent e) {
		popup.show(e.getComponent(), e.getX(), e.getY());
	}
	
	public void showPopup(Component c, int x, int y){
		popup.show(c, x, y);
	}

	@Override
	/**
	 * Thread that polls the server for PostIt updates. 
	 */
	public void run() {
		while (true) {
			try {
				InputStream in;
				// Blocking call. 
				in = WebUtil.pullPostItUpdates();

				StringWriter writer = new StringWriter();
				IOUtils.copy(in, writer); 
				String message = writer.toString();

				// Just delegate the work to the processor thread. 
				synchronized(postItUpdates) {
					//UNCOMMENT THIS ERIC
					postItUpdates.add(message);
					postItUpdates.notifyAll();
				}
			} catch (IOException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e){
				//System.out.println("BoardPane thread " + e.getMessage());
			}
		}
	}

	@Override
	public void componentHidden(ComponentEvent e) {
	}

	public boolean overlapsWithForbiddenAreas (PostIt pi) {
		return overlapsWithForbiddenAreas(pi.getBounds());
		
	}
	
	public boolean overlapsWithForbiddenAreas (Rectangle r) {
		boolean primaryCheck = false;
		boolean secondaryCheck = false;
		
		for(Component c : this.getComponents()){
			if(c == plusArea){
				primaryCheck = true;
			}
			else if(c == plusAreaSecondary){
				secondaryCheck = true;
			}
		}
		
		if(primaryCheck && secondaryCheck){
			return r.intersects(plusArea.getBounds()) || r.intersects(plusAreaSecondary.getBounds()) ||
					r.intersects(chatPanel.getBounds());
		}
		else if(primaryCheck){
			return r.intersects(plusArea.getBounds()) ||
					r.intersects(chatPanel.getBounds());
		}
		else if(secondaryCheck){
			return r.intersects(plusAreaSecondary.getBounds()) ||
					r.intersects(chatPanel.getBounds());
		}
		else{
			return r.intersects(chatPanel.getBounds());
		}
	}
	
	public boolean rectOverlapsWithForbiddenAreas (Rectangle r) {
		return r.intersects(plusArea.getBounds()) || r.intersects(plusAreaSecondary.getBounds()) ||
				r.intersects(chatPanel.getBounds());
	}
	
	public Point getClosestGoodLocation(PostIt pi){
		Rectangle r = new Rectangle();
		int max = this.getWidth();
		
		for(int i = 0; i < max; i++){
			int x = pi.getX() + i;
			int y = pi.getY() + i;
			
			if(x < this.getWidth() - pi.getWidth()){
				r = new Rectangle(x, pi.getY(), pi.getWidth(), pi.getHeight());
				if(!rectOverlapsWithForbiddenAreas(r)){
					return r.getLocation();
				}
			}
			if(pi.getX() + (pi.getX() - x) > 0){
				r = new Rectangle(pi.getX() + (pi.getX() - x), pi.getY(), pi.getWidth(), pi.getHeight());
				if(!rectOverlapsWithForbiddenAreas(r)){
					return r.getLocation();
				}
			}
			if(y < this.getHeight() - pi.getHeight()){
				r = new Rectangle(pi.getX(), y, pi.getWidth(), pi.getHeight());
				if(!rectOverlapsWithForbiddenAreas(r)){
					return r.getLocation();
				}
			}
			if(pi.getY() + (pi.getY() - y) > 0){
				r = new Rectangle(pi.getX(), pi.getY() + (pi.getY() - y), pi.getWidth(), pi.getHeight());
				if(!rectOverlapsWithForbiddenAreas(r)){
					return r.getLocation();
				}
			}
		}
		
		return null;
	}

	public int getChatAreaWidth () {
		return chatPanel.getWidth();
	}

	@Override
	/**
	 * For listen to movement (dragging) of PostIt objects. Then regroup things accordingly. 
	 * 
	 * -Eric, changed to reflect possibility of multiple movements of postits at a time
	 */
	public void componentMoved(ComponentEvent e) {
		if (e.getSource() instanceof PostIt){
			this.repaint();
		}	
	}

	public static List<PostIt> findOverlaps(PostIt pi) {
		List<PostIt> overlaps = new LinkedList<PostIt>();
		for (PostIt postIt : allPostIts) {
			if (overlaps(postIt, pi) && postIt != pi) {
				overlaps.add(postIt);
			}
		}
		overlaps.add(pi);
		return overlaps;
	}
	
	public static List<PostIt> findOverlaps(Rectangle r) {
		List<PostIt> overlaps = new LinkedList<PostIt>();
		for (PostIt postIt : allPostIts) {
			if (postIt.getBounds().intersects(r)) {
				overlaps.add(postIt);
			}
		}
		return overlaps;
	}

	private void mergeIntoOneGroup(List<PostIt> overlaps, String groupToMergeInto, PostIt topMost) {
		boolean sameGroup = true;
		boolean invalidGroupSeen = false;
		String lastGroup = null;
		for (PostIt postIt : overlaps) {
			invalidGroupSeen = invalidGroupSeen || postIt.getGroup().equals(INVALID_GROUP);
			if (lastGroup == null){
				lastGroup = postIt.getGroup();
				continue;
			}
			sameGroup = sameGroup && lastGroup.equals(postIt.getGroup());
			if (! sameGroup)
				break;
			lastGroup = postIt.getGroup();
		}
		
		if (sameGroup && ! invalidGroupSeen) {
			// check if there's only 1 post it. if so change group. 
			if (overlaps.size() == 1){
				//ERIC
				//Check if this solitary postit was originally from a pile	
				for(PostIt p : getAllPostIts()){
					if(overlaps.get(0).getGroup().equals(p.getGroup()) && p != overlaps.get(0)){
						mergeHelper(overlaps, groupToMergeInto);
					}
				}
			}
			this.cascadePostIts(overlaps, topMost);
		} else {
			mergeHelper(overlaps, groupToMergeInto);
		}
		this.cascadePostIts(overlaps, topMost);
	}

	private void mergeHelper (List<PostIt> overlaps, String groupToMergeInto) {
		try{
			String nextGroup = "";
			
			for (PostIt postIt : overlaps) {
				this.removeFromGroup(postIt);
				
				if(groupToMergeInto.equals("")){
					//TURN ANY PREVIOUS GROUP LINKS INTO SINGLE LINKS
					String groupToAbsorb = postIt.getGroup();
					
					for(String key : groupIDMap.keySet()){
						//Checking key side of ID map
						if(key.equals(groupToAbsorb)){
							singlePostIt = postIt;
							for(String group : groupIDMap.get(key)){
								for(PostIt item: postItGroups.get(group)){
									if(singlePostIt == null){
										singlePostIt = postIt;
										singleLink(item, null);
									}
									else{
										singleLink(item, null);
									}
								}
							}
						}
						//Checking value side of ID map
						else if(groupIDMap.get(key).contains(groupToAbsorb)){
							if(postItGroups.get(key) != null){
								for(PostIt item : postItGroups.get(key)){
									singlePostIt = item;
									if(singlePostIt == null){
										singlePostIt = item;
										singleLink(postIt, null);
									}
									else{
										singleLink(postIt, null);
									}
								}
							}
						}
					}
					
					singlePostIt = null;
					
					nextGroup = Long.toString(this.getNextGroup());
					postIt.setGroup(nextGroup);
					
					
					//updateGroupMap();
				}
				else if(groupToMergeInto.equals("DO NOT CHANGE GROUP")){
					System.out.println("didnt change the group!!!!");
					nextGroup = "";
					//updateGroupMap();
				}
				else{
					
					nextGroup = groupToMergeInto;
					
					//Preserve old group links even with new groupID
					String groupToAbsorb = postIt.getGroup();
					for(String list : ((HashMap<String, List<String>>)groupIDMap.clone()).keySet()){
						if(groupIDMap.get(list).contains(groupToAbsorb)){
							groupIDMap.get(list).remove(groupToAbsorb);
								ArrayList<ArrayList<String>> toRemove = new ArrayList<ArrayList<String>>();
								for(ArrayList<String> l : groupSizeMap.keySet()){
									if(l.contains(list) && l.contains(groupToAbsorb)){
											toRemove.add(l);
									}
								}
								for(ArrayList<String> l : toRemove){
									//groupIDMap.remove(l); errrmmm
									groupSizeMap.remove(l);
								}
							
							groupIDMap.get(list).add(nextGroup);
							
								ArrayList<String> sizeMapOut = new ArrayList<String>();
								sizeMapOut.add(list);
								sizeMapOut.add(nextGroup);
								groupSizeMap.put(sizeMapOut, linkSize);
	
						}
						else if(list.equals(groupToAbsorb)){
							groupIDMap.put(nextGroup, groupIDMap.get(list));
							for(String s : groupIDMap.get(list)){
								ArrayList<String> sizeMapOut = new ArrayList<String>();
								sizeMapOut.add(nextGroup);
								sizeMapOut.add(s);
								groupSizeMap.put(sizeMapOut, linkSize);
							}
							postItGroups.remove(list);
						}
					}
					
					postIt.setGroup(groupToMergeInto);
					
					//updateGroupMap();
					
				}
			}
			
			postItGroups.put(nextGroup, overlaps);
		}
		catch(ConcurrentModificationException e){
			//Do nothing for now
		}
	}
	

	/**
	 * Finds all the overlapping PostIt and group them together in a new group. 
	 * If pi belongs to some current group it is removed.
	 * @param pi
	 * @param group
	 */
	public void determineAndAddToGroup (PostIt pi, Point location) {
		List<PostIt> overlaps = this.findOverlaps(pi);
		String groupToMergeInto = "";
		boolean dragEnabled = false;
		
		
		for (PostIt postIt: allPostIts) {
			if (postIt.dragEnabled()) {
				dragEnabled = true;
			}
		}
		
		for (PostIt postIt: overlaps) {
			if (postIt != pi && !dragEnabled) {
				groupToMergeInto = postIt.getGroup();
			}
		}
		
		for(PostIt p : getAllPostIts()){
			if(p.getGroup().equals(groupToMergeInto) && !overlaps.contains(p)){
				overlaps.add(p);
			}
			else if(!p.getGroup().equals(groupToMergeInto) && p != pi){
				overlaps.remove(p);
			}
		}
		
		if(overlaps.size() > 0){
			this.mergeIntoOneGroup(overlaps, groupToMergeInto, pi);
		}
	}
	

	public boolean canGroupAccept(PostIt pi){
		List<PostIt> overlaps = this.findOverlaps(pi);
		boolean dragEnabled = false;
		
		String groupToMergeInto = "";
		
		for (PostIt postIt: allPostIts) {
			if (postIt.dragEnabled()) {
				dragEnabled = true;
			}
		}
		for (PostIt postIt: overlaps) {
			if (postIt != pi && !dragEnabled) {
				groupToMergeInto = postIt.getGroup();
			}
		}
		
		PostIt topLeft = null;
		PostIt bottomRight = null;
		for(PostIt p: allPostIts){
			if(p.getGroup().equals(groupToMergeInto)){
				if(topLeft == null && bottomRight == null){
					topLeft = p;
					bottomRight = p;
				}
				else if(p.getY() < topLeft.getY()){
					topLeft = p;
				}
				else if(p.getY() > bottomRight.getY()){
					bottomRight = p;
				}
			}
		}
		try{
			Rectangle tL = topLeft.getBounds();
			tL.setBounds(((int) tL.getX() - dx), ((int) tL.getY() - dy), (int)tL.getWidth(), (int)tL.getHeight());
			Rectangle bR = topLeft.getBounds();
			tL.setBounds(((int) tL.getX() + dx), ((int) tL.getY() + dy), (int)tL.getWidth(), (int)tL.getHeight());
			
			if(overlapsWithForbiddenAreas(tL) && overlapsWithForbiddenAreas(bR)){
				return false;
			}
			else{
				return true;
			}
		} catch(NullPointerException e){
			return true;
		}
	}
	
	/**
	 * If new pile with parameter postit is over a illegal area, move whole pile 
	 * @param inPost
	 */
	public void checkAndShiftGroup(PostIt inPost){
		ArrayList<PostIt> groupList = new ArrayList<PostIt>();
		
		for(PostIt pi : getAllPostIts()){
			if(pi.getGroup().equals(inPost.getGroup())){
				groupList.add(pi);
			}
		}
		
		boolean shouldShift = false;
		for(PostIt pi : groupList){
			if(overlapsWithForbiddenAreas(pi)){
				shouldShift = true;
			}
			for(PostIt overlap : findOverlaps(pi)){
				if(!overlap.getGroup().equals(pi.getGroup())){
					shouldShift = true;
				}
			}
		}
		
		if(shouldShift){
			int dXdYMulti = 0;
			
			boolean continueSearching = false;
			for(int i = 1; i < (this.getWidth() / dx); i++){
				
				for(PostIt p : groupList){
					Rectangle upLeft = new Rectangle(p.getX() + (i * dx), 
							p.getY() + (i * dy), p.getWidth(), p.getHeight());
							
					if(overlapsWithForbiddenAreas(upLeft)){
						continueSearching = true;
					}
					for(PostIt overlap : findOverlaps(upLeft)){
						if(!overlap.getGroup().equals(p.getGroup())){
							continueSearching = true;
						}
					}
					
				}
					
				if(!continueSearching){
					dXdYMulti = i;
					break;
				} else{
					continueSearching = false;
				}
				
				for(PostIt p : groupList){
					Rectangle botRight = new Rectangle(p.getX() - (i * dx), 
							p.getY() - (i * dy), p.getWidth(), p.getHeight());
					if(overlapsWithForbiddenAreas(botRight)){
						continueSearching = true;
					}
					for(PostIt overlap : findOverlaps(botRight)){
						if(!overlap.getGroup().equals(p.getGroup())){
							continueSearching = true;
						}
					}
				}
				
				if(!continueSearching){
					dXdYMulti = i * -1;
					break;
				} else{
					continueSearching = false;
				}
				
			}
			
			//Apply multiplier to dx/dy which will put stack in legal position
			for(PostIt p : groupList){
				p.setLocation(p.getX() + (dx * dXdYMulti), p.getY() + (dy * dXdYMulti));
			}
		}
	}

	private static boolean overlaps(PostIt postIt, PostIt pi) {
		return pi.getBounds().intersects(postIt.getBounds()) && postIt != pi;
	}

	@Override
	public void componentResized(ComponentEvent e) {
	}

	@Override
	public void componentShown(ComponentEvent e) {
	}

	@Override
	public void focusGained(FocusEvent fe) {
	}

	@Override
	public void focusLost(FocusEvent fe) {
	}

	@Override
	public void internalFrameActivated(InternalFrameEvent fe) {
		PostIt pi = (PostIt) fe.getSource();
		stopEditing();

		postItInFocus = pi;	
		
		
		//SaveState
			HashMap<String, Object> save = new HashMap<String, Object>();
			save.put("post", postItInFocus);
			JTextPane a = postItInFocus.getArea();
			
			String title = "";
			
				if(postItInFocus.titleEndPosition() > 0){
					title =  postItInFocus.getText().substring(0, postItInFocus.titleEndPosition() - 1);
				}
				
				String text = a.getText().substring(postItInFocus.titleEndPosition());
				
				save.put("title", a.getText().substring(0, postItInFocus.titleEndPosition()));
				if(a.getText().substring(postItInFocus.titleEndPosition()) == null){
					save.put("text", "");
				}
				else{
					save.put("text", a.getText().substring(postItInFocus.titleEndPosition()));
				}
				
				if(fe.getSource() instanceof JTextPane){
					BoardPane.getInstance().saveState("textUpdate", save);
				}
				
				String group = pi.getGroup();
				
		if(this.findOverlaps(pi).size() > 1){
			List<PostIt> list = this.findOverlaps(pi);
			this.popupEditor.addPostIts(pi, list);
		} else {
			// clicked on individual post it. make it editable.
			pi.makeEditable ();
		}
	}



	@Override
	public void internalFrameClosed(InternalFrameEvent arg0) {

	}

	@Override
	public void internalFrameClosing(InternalFrameEvent arg0) {

	}

	@Override
	public void internalFrameDeactivated(InternalFrameEvent fe) {
	}


	@Override
	public void internalFrameDeiconified(InternalFrameEvent arg0) {

	}

	@Override
	public void internalFrameIconified(InternalFrameEvent arg0) {

	}

	@Override
	public void internalFrameOpened(InternalFrameEvent arg0) {
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		boolean dragEnabled = false;
		PostIt post = null;
		
		for(PostIt p: allPostIts){
			if(p.dragEnabled()){
				dragEnabled = true;
				post = p;
			}
		}
		
		if(dragEnabled){
			for(PostIt p: getAllPostIts()){
				if((p.isSelect() && post.isSelect()) || p == post){
					if(p == post){
						p.setLocation(p.getOrigX() + (int)(e.getXOnScreen() - p.getMousePointX()), 
								p.getOrigY() + (int)(e.getYOnScreen() - p.getMousePointY()));
					}
					else{
						p.setLocation(p.getOrigX() + (int)(e.getXOnScreen() - p.getMousePointX() ), 
								p.getOrigY() + (int)(e.getYOnScreen() - p.getMousePointY() ));
					}
				}
			}
		}
		else if (selecting) {
			mouseRect.setBounds(
					Math.min(mousePt.x, e.getX()),
					Math.min(mousePt.y, e.getY()),
					Math.abs(mousePt.x - e.getX()),
					Math.abs(mousePt.y - e.getY()));
			PostIt.selectRect(allPostIts, mouseRect);

		} else {
			delta.setLocation(
					e.getX() - mousePt.x,
					e.getY() - mousePt.y);
			mousePt = e.getPoint();
		}
		e.getComponent().repaint();

	}

	@Override
	public void mouseMoved(MouseEvent arg0) {
	}	

	private PostIt getPostItInPlusArea () {
		for(PostIt pi : allPostIts) {
			if (pi.getBounds().intersects(plusArea.getBounds()))
				return pi;
		}
		return null;
	}
	
	private PostIt getPostItInPlusAreaSecondary () {
		for(PostIt pi : allPostIts) {
			if (pi.getBounds().intersects(plusAreaSecondary.getBounds()))
				return pi;
		}
		return null;
	}

	public boolean canCreateLocalPostIt () {
		if(InfoViz.UserID.equals("Pat")){
			return getPostItInPlusAreaSecondary () == null;
		}
		else{
			return getPostItInPlusArea () == null;
		}
	}

	public void keyTyped(KeyEvent e) {
	}

	public void keyPressed(KeyEvent e) {
	}

	public void keyReleased(KeyEvent e) {
	}
	
	

	//ERIC
	@Override
	public void paintComponent(Graphics g) {
		try{
			g.setColor(Color.LIGHT_GRAY);
			g.fillRect(0, 0, getWidth(), getHeight());
			
			if (selecting) {
				g.setColor(Color.darkGray);
	
				g.drawRect(mouseRect.x, mouseRect.y,
						mouseRect.width, mouseRect.height);
	
			}
	
			g.setColor(Color.darkGray);
	
			//Regular stroke
			
			Graphics2D g2 = (Graphics2D) g;
			
			g2.setColor(Color.gray);
			
			//Strong stroke
			BasicStroke strong = new BasicStroke(4.0f,
					BasicStroke.CAP_ROUND,
					BasicStroke.JOIN_ROUND);	
			
	
			for(postItPair p: ((ArrayList<postItPair>)singleLinkMap.clone())){
				if(p.a.getForeignSharing() && p.b.getForeignSharing()){
					BasicStroke s;
					if(singleSizeMap.get(p) != null){
						s = new BasicStroke(singleSizeMap.get(p),
								BasicStroke.CAP_ROUND,
								BasicStroke.JOIN_ROUND);
					}
					else{
						s = new BasicStroke(2.0f,
								BasicStroke.CAP_ROUND,
								BasicStroke.JOIN_ROUND);
					}
					
					g2.setStroke(s);
							
					double Ax = p.a.getX() + (p.a.getSize().width / 2);
					double Ay = p.a.getY() + (p.a.getSize().height / 2);
					double Bx =  p.b.getX() + (p.b.getSize().width / 2);
					double By = p.b.getY() + (p.b.getSize().height / 2);
					
					
					double slope1 = (Ay - By)/(Ax - Bx);
					double slope2 = (Ay - currMeY)/(Ax - currMeX);
		
					if(slope1 - slope2 < 0.2 && slope1 - slope2 > -0.2 && 
							((currMeX < Ax && currMeX > Bx) || (currMeX > Ax && currMeX < Bx)) && 
							((currMeY < Ay && currMeY > By) || (currMeY > Ay && currMeY < By)) && 
							rightClickLineRemoval){
					
					singleLinkMap.remove(p);
					singleSizeMap.remove(p);
						
						rightClickLineRemoval = false;
					}
					else{
						g2.setStroke(s);
						g2.drawLine((int)Ax, (int)Ay, (int)Bx, (int)By);
						
						boolean intersectFound = false;
						
						Line2D.Double l = new Line2D.Double(Ax, Ay, Bx, By);
						ArrayList<Line2D.Double> polygonBounds = new ArrayList<Line2D.Double>();
						
						//If postit is in a stack, create a polygon of the stack
						//former null exception here 
						if(postItGroups.get(p.b.getGroup()).size() > 1){
							//BUILD SHAPE 
							ArrayList<PostIt> so = sortPostIts(postItGroups.get(p.b.getGroup()));
							
							//Add top line
							polygonBounds.add(new Line2D.Double(so.get(0).getX(), so.get(0).getY(), so.get(0).getX() + so.get(0).getWidth(), so.get(0).getY()));
							
							//Add top-right jagged
							
							//Add right side
							PostIt lastP = so.get(so.size() - 1);
							polygonBounds.add(new Line2D.Double(so.get(0).getX() + so.get(0).getWidth(), so.get(0).getY(), lastP.getX() + lastP.getWidth(), 
									lastP.getY() + lastP.getHeight()));
							
							//Backtrack to rightside if necessary
							
							//Add bottom line
							polygonBounds.add(new Line2D.Double(lastP.getX() + lastP.getWidth(), lastP.getY() + lastP.getHeight(), lastP.getX(), 
									lastP.getY() + lastP.getHeight()));
							
							//Jagged bottom left
							
							//Left line
							polygonBounds.add(new Line2D.Double(lastP.getX(), lastP.getY() + lastP.getHeight(), so.get(0).getX(), so.get(0).getY()));
						}
						//Postit is not in a stack, hence is only a rectangle with 4 sides
						else{
							//Left Side
							polygonBounds.add(new Line2D.Double(p.b.getX(), p.b.getY(), p.b.getX(), p.b.getY() + p.b.getHeight()));
							//Right Side
							polygonBounds.add(new Line2D.Double(p.b.getX() + p.b.getWidth(), p.b.getY(), p.b.getX() + p.b.getWidth(), p.b.getY() + p.b.getHeight()));
							//Top
							polygonBounds.add(new Line2D.Double(p.b.getX(), p.b.getY(), p.b.getX() + p.b.getWidth(), p.b.getY()));
							//Bottom
							polygonBounds.add(new Line2D.Double(p.b.getX(), p.b.getY() + p.b.getHeight(), p.b.getX() + p.b.getWidth(), p.b.getY() + p.b.getHeight()));
						}
						
						
						
						int rIndex = 0;
						
						while(!intersectFound && rIndex < polygonBounds.size()){
							Line2D.Double compL = polygonBounds.get(rIndex);
							if(l.intersectsLine(compL)){
								//Determine intersect of l and compL
								Point intersect = getLineLineIntersection(l.x1, l.y1, l.x2, l.y2, compL.x1, compL.y1, compL.x2, compL.y2);
								
								//Midpoint
								Point midpoint = new Point();
								midpoint.setLocation((int) (0.5 * (Ax + Bx)), (int) (0.5 * (Ay + By)));
								drawArrowHead(g2, midpoint, new Point((int)Ax, (int)Ay), g2.getColor());
								
								//Endpoint
								drawArrowHead(g2, intersect, new Point((int)Ax, (int)Ay), g2.getColor());
								
								intersectFound = true;
							}
		
							rIndex++;
						}
						
					}	
				}
			}
						
			g2.setColor(Color.black);
			String rightRemovalKey = null;
			String rightRemovalValue = null;
			
			for(String post : groupIDMap.keySet()){
			//FOR EVERY COMBINATION
			if(postItGroups.get(post) != null ){
				List<PostIt> pList = postItGroups.get(post);
				for(String combo : groupIDMap.get(post)){
				
					int halfWidth = 0;
					int halfHeight1 = 0;
					int halfHeight2 = 0;
		
					//Determine average point of key
					double avgXK = 0.0;
					double avgYK = 0.0;
	
					for(PostIt p : postItGroups.get(post)){
						avgXK = avgXK + p.getX();
						avgYK = avgYK + p.getY();
		
						halfWidth = halfWidth + (p.getWidth() / 2);
						halfHeight1 = halfHeight1 + (p.getHeight() / 2);
					}
						halfWidth = halfWidth / postItGroups.get(post).size();
						halfHeight1 = halfHeight1 / postItGroups.get(post).size();
					avgXK = avgXK / pList.size();
					avgYK = avgYK / pList.size();
		
		
					//Determine average point of value
					double avgXV = 0.0;
					double avgYV = 0.0;
					
					
					//Problem child
					if(postItGroups.get(combo) != null){
						for(PostIt p : postItGroups.get(combo)){
							avgXV = avgXV + p.getX();
							avgYV = avgYV + p.getY();
							halfHeight2 = halfHeight2 + (p.getHeight() / 2);
						}
							halfHeight2 = halfHeight2 / postItGroups.get(combo).size();
						avgXV = avgXV / postItGroups.get(combo).size();
						avgYV = avgYV / postItGroups.get(combo).size();
					}
					
					double slope1 = ((avgYK + halfHeight1) - (avgYV + halfHeight2))/((avgXK + halfWidth) - (avgXV + halfWidth));
					double slope2 = ((avgYK + halfHeight1) - currMeY)/((avgXK + halfWidth) - currMeX);
	
					if(slope1 - slope2 < 0.2 && slope1 - slope2 > -0.2 && 
							((currMeX < (avgXK + halfWidth) && currMeX > (avgXV + halfWidth)) || 
									(currMeX > (avgXK + halfWidth) && currMeX < (avgXV + halfWidth))) && 
							((currMeY < (avgYK + halfHeight1) && currMeY > (avgYV + halfHeight2)) ||
									(currMeY > (avgYK + halfHeight1) && currMeY < (avgYV + halfHeight2))) && 
							rightClickLineRemoval){
						
						rightRemovalKey = post;
						rightRemovalValue = combo;
						
						ArrayList<ArrayList<String>> toRemove = new ArrayList<ArrayList<String>>();
						for(ArrayList<String> l : groupSizeMap.keySet()){
							if(l.contains(post) && l.contains(combo)){
									toRemove.add(l);
							}
						}
						for(ArrayList<String> l : toRemove){
							groupSizeMap.remove(l);
						}
							
						rightClickLineRemoval = false;
					}
					else{	
						BasicStroke s;
						ArrayList<String> sizeMapIn = new ArrayList<String>();
						sizeMapIn.add(post);
						sizeMapIn.add(combo);
		
						if(groupSizeMap.get(sizeMapIn) != null){
							s = new BasicStroke(groupSizeMap.get(sizeMapIn),
									BasicStroke.CAP_ROUND,
									BasicStroke.JOIN_ROUND);
						}
						else{
							s = new BasicStroke(2.0f,
									BasicStroke.CAP_ROUND,
									BasicStroke.JOIN_ROUND);
						}
						
						g2.setStroke(s);
						g2.drawLine((int)avgXK + halfWidth, (int)avgYK + halfHeight1, 
								(int)avgXV + halfWidth, (int)avgYV + halfHeight2);
						
						boolean intersectFound = false;
						
						Line2D.Double l = new Line2D.Double((int)avgXK + halfWidth, (int)avgYK + halfHeight1, 
								(int)avgXV + halfWidth, (int)avgYV + halfHeight2);
						ArrayList<Line2D.Double> polygonBounds = new ArrayList<Line2D.Double>();
						
						PostIt bottom = null;
						
						//If postit is in a stack, choose the lowest element in the stack
							ArrayList<PostIt> so = sortPostIts(postItGroups.get(combo));
							if(so.size() > 0){
								bottom = so.get(so.size() - 1);
							}
							else{
								bottom = null;
							}
						
						
						if(bottom != null){
							//Bottommost post it provides bounds for arrow
								//Left Side
								polygonBounds.add(new Line2D.Double(bottom.getX(), bottom.getY(), bottom.getX(), bottom.getY() + bottom.getHeight()));
								//Right Side
								polygonBounds.add(new Line2D.Double(bottom.getX() + bottom.getWidth(), bottom.getY(), bottom.getX() + bottom.getWidth(),
										bottom.getY() + bottom.getHeight()));
								//Top
								polygonBounds.add(new Line2D.Double(bottom.getX(), bottom.getY(), bottom.getX() + bottom.getWidth(), bottom.getY()));
								//Bottom
								polygonBounds.add(new Line2D.Double(bottom.getX(), bottom.getY() + bottom.getHeight(), bottom.getX() + bottom.getWidth(),
										bottom.getY() + bottom.getHeight()));
							
							int rIndex = 0;
							
							while(!intersectFound && rIndex < polygonBounds.size()){
								Line2D.Double compL = polygonBounds.get(rIndex);
								if(l.intersectsLine(compL)){
									
									//Determine intersect of l and compL
									Point intersect = getLineLineIntersection(l.x1, l.y1, l.x2, l.y2, compL.x1, compL.y1, compL.x2, compL.y2);
									
									//Midpoint
									Point midpoint = new Point();
									midpoint.setLocation((int) (0.5 * (avgXK + halfWidth + avgXV + halfWidth)), (int) (0.5 * (avgYK + halfHeight1 + avgYV + halfHeight2)));
									drawArrowHead(g2, midpoint, new Point((int)avgXK + halfWidth, (int)avgYK + halfHeight1), g2.getColor());
									
									//Endpoint
									drawArrowHead(g2, intersect, new Point((int)avgXK + halfWidth, (int)avgYK + halfHeight1), g2.getColor());
									
									intersectFound = true;
								}
			
								rIndex++;
							}
						}
					}
				}
			}
			}
			
			//If a right click removal for group links was used (put here to prevent concurrent modification error)
			if(rightRemovalKey != null && rightRemovalValue != null){
				groupIDMap.get(rightRemovalKey).remove(rightRemovalValue);
			}
		}
		catch(ArithmeticException e){
			
		}
		catch(NullPointerException e){
			
		}
	}
	
	private static ArrayList<PostIt> sortPostIts(List<PostIt> unbuffered){
		ArrayList<PostIt> a = new ArrayList<PostIt>();
		
		//Guard against null
		if(unbuffered == null){
			return a;
		}
		a.addAll(unbuffered);
		
		ArrayList<PostIt> sorted = new ArrayList<PostIt>();
		
		int iMin;
		 
		while(a.size() > 0){
			iMin = 0;
			
			for(int index = 1; index < a.size(); index++){
				if(a.get(index).getX() <= a.get(iMin).getX()){
					iMin = index;
				}
			}
			
			sorted.add(a.get(iMin));
			a.remove(iMin);
		}
		
		return sorted;
	}
	
	 public static Point getLineLineIntersection(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) {
	      double det1And2 = det(x1, y1, x2, y2);
	      double det3And4 = det(x3, y3, x4, y4);
	      double x1LessX2 = x1 - x2;
	      double y1LessY2 = y1 - y2;
	      double x3LessX4 = x3 - x4;
	      double y3LessY4 = y3 - y4;
	      double det1Less2And3Less4 = det(x1LessX2, y1LessY2, x3LessX4, y3LessY4);
	      if (det1Less2And3Less4 == 0){
	         // the denominator is zero so the lines are parallel and there's either no solution (or multiple solutions if the lines overlap) so return null.
	         return null;
	      }
	      double x = (det(det1And2, x1LessX2,
	            det3And4, x3LessX4) /
	            det1Less2And3Less4);
	      double y = (det(det1And2, y1LessY2,
	            det3And4, y3LessY4) /
	            det1Less2And3Less4);
	      return new Point((int)x, (int)y);
	   }
	   protected static double det(double a, double b, double c, double d) {
	      return a * d - b * c;
	   }
	   

	//Creates a single link between pairs of either singular postits or groups of selected postits
	// p is a singular postIt that is attempting to make a single link(s)
	// l is if a number of selected postits are included in making single links
	// only either p or l is not null when this method is called
	//THIS METHOD MUST BE CALLED TWICE TO CREATE A LINK, once for all postits with the outlink, and once for all 
	//postits with the inlink
	public static void singleLink(PostIt p, List<PostIt> l){

		if(singlePostIt != null || singlePostItList != null){
			//Save state prior to creating links
				HashMap<String, Object> mapOut = new HashMap<String, Object>();
				mapOut.put("singleList", singleLinkMap.clone());
				BoardPane.getInstance().saveState("singleLink", mapOut);
		}
		
		if(singlePostIt != null){
			if(p != null){
				singleLinkHelper(p, singlePostIt);
			}
			else{
				for(PostIt post: l){
					singleLinkHelper(post, singlePostIt);
				}
			}
			singlePostIt = null;
		}
		else if(singlePostItList != null){
			for(PostIt listPost : singlePostItList){
				if(p != null){
					singleLinkHelper(p, listPost);
				}
				else{
					for(PostIt post: l){
						singleLinkHelper(post, listPost);
					}
				}
			}
			singlePostItList = null;
		}
		else{
			if(p != null){
				singlePostIt = p;
			}
			else{
				singlePostItList = l;
			}
		}
		BoardPane.getInstance().repaint();
	}
	
	//Single link maker helper method called in singlelink method.
	public static void singleLinkHelper(PostIt p, PostIt pairing){
		boolean pairEstablished = false;
		
		for(postItPair pair: singleLinkMap){
			if(pair.a == pairing && pair.b == p
					|| pair.a == p && pair.b == pairing){
				System.out.println("PAIR ESTABLISHED AT ");
				pairEstablished = true;

			}
		}

		if(!pairEstablished){
			System.out.println("NOT A PREESTABLISHED PAIR");
			postItPair pairOut = new postItPair(pairing, p);

			singleLinkMap.add(pairOut);
			singleSizeMap.put(pairOut, linkSize);
		}
	}
	
	public static List<PostIt> getAllPostIts(){
		return allPostIts;
	}
	
	public static class postItPair{
			public PostIt a;
			public PostIt b;

			public postItPair(PostIt a, PostIt b){
				this.a = a;
				this.b = b;
			}
			
			public boolean contains(PostIt a){
				return this.a == a || this.b == a;
			}
	}



		@Override
		public void stateChanged(ChangeEvent e) {
			  Color newColor = colorChooser.getColor();
			  PostIt.chooseColor(newColor);
		}
		
		/**
		 * Commented out for the moment
		 * @param s
		 * @param o
		 */
		public void saveState(String s, HashMap<String, Object> o){
			HashMap mapOut = new HashMap<String, Object>();
			mapOut.put(s, o);
			actionList.add(0, mapOut);
		}
		
		public void undo(){
			HashMap<String, HashMap<String, Object>> mapIn = actionList.get(0);
			String s = null;
			
			//Only one string and object should be found
			for(String str : mapIn.keySet()){
				s = str;
			}
			
			HashMap<String, Object> aMap = mapIn.get(s);
			
			System.out.println("UNDOING: " + s);
			
			if(s.equals("delete")){
					HashSet<PostIt> postSet = (HashSet<PostIt>) aMap.get("postSet");
					ArrayList<postItPair> singleList = (ArrayList<postItPair>) aMap.get("singleList");
					Hashtable<String, List<PostIt>> groupList = (Hashtable<String, List<PostIt>>) aMap.get("groups");
					HashMap<String, List<String>> groupMap = (HashMap<String, List<String>>) aMap.get("groupMap");
					Hashtable<AnnotationUI, PostIt> a2p = (Hashtable<AnnotationUI, PostIt>) aMap.get("a2p");
					Hashtable<PostIt, AnnotationUI> p2a = (Hashtable<PostIt, AnnotationUI>) aMap.get("p2a");
					
					singleLinkMap = singleList;
					postItGroups = groupList;
					groupIDMap = groupMap;
					LeftRightCommunicator.post2Anno = p2a;
					LeftRightCommunicator.anno2Post = a2p;
					
					for(PostIt p : postSet){		
						
						Point pointTemp = p.getLocation();
						
						final PostIt pi = new PostIt(InfoViz.userToken, false);
							
						pi.setBounds((int) pointTemp.getX(), (int)pointTemp.getY(), pi.getWidth(), pi.getHeight());
						if (this.overlapsWithForbiddenAreas(pi)) {
							// Ignore mouse clicks that ends up creating post it that 
							// overlaps with plus area or the chat area. 
							return; 
						}
						addPostIt(pi, pointTemp);
						try {
							pi.setSelected(true);
						} catch (PropertyVetoException e) {
						}
						pi.toFront();
						
						pi.setGroup(p.getGroup());
						pi.setSessionID(p.getSessionId());
						pi.getArea().setStyledDocument(p.getArea().getStyledDocument());
						
						//Replace in singleLinkMap
						for(postItPair pair : singleLinkMap){
							if(pair.a.getSessionId() == p.getSessionId()){
								pair.a = pi;
							}
							else if(pair.b.getSessionId() == p.getSessionId()){
								pair.b = pi;
							}
						}
						
						//Correct groupIDMap
						for(String inbound: p.getInbound()){
							groupIDMap.get(inbound).add(p.getGroup());
						}
						
						//Replace in PostitGroups
						HashMap<String, List<PostIt>> toRemove = new HashMap<String, List<PostIt>>();
						for(String group:  postItGroups.keySet()){
							List<PostIt> pList = new ArrayList<PostIt>();
							pList = postItGroups.get(group);
							
							ArrayList<PostIt> delList = new ArrayList<PostIt>();
							for(PostIt post: pList){
								if(post.getSessionId() == p.getSessionId()){
									delList.add(post);
								}
							}
							
							if(delList.size() > 0){
								toRemove.put(group, delList);
							}
						}
						for(String group: toRemove.keySet()){
							for(PostIt post: toRemove.get(group)){
								postItGroups.get(group).remove(post);
								
								if(postItGroups.get(group).size() < 1){
									postItGroups.remove(group);
								}
							}
						}	
						postItGroups.get(pi.getGroup()).add(pi);
						
						//Correct post2Anno
						HashSet<PostIt> toRemovePost = new HashSet<PostIt>();
						for(PostIt post: ((Hashtable<PostIt, AnnotationUI>)LeftRightCommunicator.post2Anno.clone()).keySet()){
							if(post.getSessionId() == p.getSessionId()){
								toRemovePost.add(post);
								LeftRightCommunicator.post2Anno.put(pi, LeftRightCommunicator.post2Anno.get(post));
								
								if(((Object) LeftRightCommunicator.post2Anno.get(post)) instanceof DocAnnotationUI){
									((DocAnnotationUI) LeftRightCommunicator.post2Anno.get(post)).deleteUndo();
								}
							}
						}
						for(PostIt post: toRemovePost){
							LeftRightCommunicator.post2Anno.remove(post);
						}
						
						//Correct anno2Post
						LeftRightCommunicator.anno2Post = a2p;
						HashSet<AnnotationUI> toRemoveAnno = new HashSet<AnnotationUI>();
						for(AnnotationUI anno: ((Hashtable<AnnotationUI, PostIt>) LeftRightCommunicator.anno2Post.clone()).keySet()){
							if(LeftRightCommunicator.anno2Post.get(anno).getSessionId() == p.getSessionId()){
								toRemoveAnno.add(anno);
								LeftRightCommunicator.anno2Post.put(anno, LeftRightCommunicator.anno2Post.get(anno));
							}
						}
						for(AnnotationUI anno: toRemoveAnno){
							LeftRightCommunicator.anno2Post.remove(anno);
							DocAnnotationUI DA = (DocAnnotationUI) anno;
						}
						
					}
					
					
			}
			else if(s.equals("create")){
					PostIt p = (PostIt) aMap.get("post");
					removePostIt(p);
				
			}
			else if(s.equals("unlink")){
					ArrayList<postItPair> singleList = (ArrayList<postItPair>) aMap.get("singleList");
					Hashtable<String, List<PostIt>> groupList = (Hashtable<String, List<PostIt>>) aMap.get("groups");
					HashMap<String, List<String>> groupMap = (HashMap<String, List<String>>) aMap.get("groupMap");
					PostIt source = ((PostIt) aMap.get("source"));
					
					singleLinkMap = singleList;
					postItGroups = groupList;
					groupIDMap = groupMap;
					
					//Correct groupIDMap
					for(String inbound: source.getInbound()){
						groupIDMap.get(inbound).add(source.getGroup());
					}
			}
			else if(s.equals("singleLink")){
					ArrayList<postItPair> singleList = (ArrayList<postItPair>) aMap.get("singleList");	
					
					singleLinkMap = singleList;
			}
			else if(s.equals("groupLink")){
					Hashtable<String, List<PostIt>> groupList = (Hashtable<String, List<PostIt>>) aMap.get("groups");
					HashMap<String, List<String>> groupMap = (HashMap<String, List<String>>) aMap.get("groupMap");
					
					postItGroups = groupList;
					groupIDMap = groupMap;
			}
			else if(s.equals("movement")){
					//Remove from any group links if they were established
					ArrayList<postItPair> singleList = (ArrayList<postItPair>) aMap.get("singleList");	
					List<PostIt> groupList = (List<PostIt>) aMap.get("groupList");
					HashMap<String, List<String>> groupMap = (HashMap<String, List<String>>) aMap.get("groupMap");
					
					//Regain previous coordinates
					Point point = (Point) aMap.get("point");
			}
			else if(s.equals("textUpdate")){
					PostIt p = (PostIt) aMap.get("post");
					String title = (String) aMap.get("title");
					String text = (String) aMap.get("text");
					
					p.restoreArea(title, text);
			}
			
			//Remove most recent action from the list
			actionList.remove(0);
			this.repaint();
		}
		
		
	 private void drawArrowHead(Graphics2D g2, Point tip, Point tail, Color color)  
	    {  
		 	double phi = Math.toRadians(40);  
	       int barb = 10;  
	        Color colorTemp = g2.getColor();
	        
	        g2.setPaint(color);  
	        double dy = tip.y - tail.y;  
	        double dx = tip.x - tail.x;  
	        double theta = Math.atan2(dy, dx);  
	        double x, y, rho = theta + phi;  
	        for(int j = 0; j < 2; j++)  
	        {  
	            x = tip.x - barb * Math.cos(rho);  
	            y = tip.y - barb * Math.sin(rho);  
	            g2.draw(new Line2D.Double(tip.x, tip.y, x, y));  
	            rho = theta - phi;  
	        }  
	        
	        g2.setColor(colorTemp);
	    }  
		 
		 /**
		  * NETWORK STRING CREATION METHODS
		  * 
		  * @param p
		  * @return
		  */
		public String getSingleLinksStringOut(PostIt p) {
			String out = "";

			for(postItPair pair : singleLinkMap){
				if(pair.a == p){
					if(singleSizeMap.containsKey(pair)){
						out = out + pair.b.getSessionId() + "|" + singleSizeMap.get(pair) + "f,";
					}
					else{
						out = out + pair.b.getSessionId() + "|" + largeSize + "f,";
					}
				}
			}
			return out;
		}
		
		public String getSingleLinksStringIn(PostIt p) {
			String out = "";
				
			for(postItPair pair : singleLinkMap){
				if(pair.b == p){
					if(singleSizeMap.containsKey(pair)){
						out = out + pair.a.getSessionId() + "|" + singleSizeMap.get(pair) + "f,";
					}
					else{
						out = out + pair.a.getSessionId() + "|" + largeSize + "f,";
					}
				}
			}
			return out;
		}
			
		public String getGroupLinkStringOut(PostIt p) throws ConcurrentModificationException, NullPointerException{
			String out = "";
			
				HashMap<String,List<String>> clone = (HashMap<String, List<String>>) groupIDMap.clone();
					
				for(String key : clone.keySet()){
					if(key == p.getGroup()){
						for(String val : groupIDMap.get(key)){
							ArrayList<String> sizeMapOut = new ArrayList<String>();
							sizeMapOut.add(key);
							sizeMapOut.add(val);
							
							if(groupSizeMap.containsKey(sizeMapOut)){
								out = out + val + "|" + groupSizeMap.get(sizeMapOut) + "f,";
							}
							else{
								out = out + val + "|" + largeSize + "f,";
							}
						}
					}	
				}
			
			return out;
		}
			
		public String getGroupLinkStringIn(PostIt p) throws ConcurrentModificationException, NullPointerException{
			String out = "";
			
				HashMap<String,List<String>> clone = (HashMap<String, List<String>>) groupIDMap.clone();
					
				for(String key : clone.keySet()){
					if(groupIDMap.get(key).contains(p.getGroup())){
						ArrayList<String> sizeMapOut = new ArrayList<String>();
						sizeMapOut.add(key);
						sizeMapOut.add(p.getGroup());
						
						if(groupSizeMap.containsKey(sizeMapOut)){
							out = out + key + "|" + groupSizeMap.get(sizeMapOut) + "f,";
						}
						else{
							out = out + key + "|" + largeSize + "f,";
						}
					}
				}
			return out;
		}

		public void postItFlash(long notificationID) {
			for(PostIt p : BoardPane.getAllPostIts()){
				if(p.getSessionId() == notificationID){
						
					postItTimerDependent = p;
					timerDependentColor = p.getArea().getBackground();
					
					int delay = 2000; //milliseconds
					  ActionListener taskPerformer = new ActionListener() {
					      public void actionPerformed(ActionEvent evt) {
					    	 postItTimerDependent.getArea().setBackground(timerDependentColor);
					      }
					  };
					  
					  p.getArea().setBackground(Color.white);
					  
					 Timer t = new Timer(delay, taskPerformer);
					 t.setRepeats(false);
					 t.start();
					 
					 int delay2 = 2000; //milliseconds
					  ActionListener taskPerformer2 = new ActionListener() {
					      public void actionPerformed(ActionEvent evt) {
					    	 postItTimerDependent.getArea().setBackground(timerDependentColor);
					      }
					  };
					  
					  p.getArea().setBackground(Color.white);
					  
					  AudioClip ac = InfoViz.newAudioClip(Util.getServerResource("resources/audio/doc.wav"));
					ac.play(); 
					try{
						Thread.sleep(300);
					}
					catch(Exception e){
						
					}
					ac.stop();
					  
					 Timer t2 = new Timer(delay2, taskPerformer2);
					 t2.setRepeats(false);
					 t2.start();
				}
			}
				
		}

		public static String getIDToGroupMap() {
			String out = "";
				
			for(PostIt p: allPostIts){
				out = out + p.getSessionId() + "^" + p.getGroup() + ",";
			}
			
			return out;
		}
			

		public static Object getPostItGroupMap() {
			String out = "";
				
			for(String key : postItGroups.keySet()){
				out = out + key + "^";
				for(PostIt p : postItGroups.get(key)){
					out = out + p.getSessionId() + ",";
				}
				out = out + "%";
			}
			
			return out;
		}

	public static Object getGroupToGroupMap() {
		String out = "";
		for(String key : groupIDMap.keySet()){
			out = out + key + "^";
				for(String value : groupIDMap.get(key)){
					out = out + value + ",";
				}
			out = out + "%";
		}
		return out;
	}
			
			
			
	public HttpChatPanel getChat(){
		return chatPanel;
	}
}

